package com.wanban.downloader.utils

import android.text.TextUtils
import android.util.Log
import com.blankj.utilcode.util.CloseUtils
import com.blankj.utilcode.util.ConvertUtils
import com.blankj.utilcode.util.FileIOUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.ThreadUtils
import com.wanban.downloader.exception.HttpConnectionException
import com.wanban.downloader.listener.OnParseListener
import com.wanban.downloader.model.M3U8
import com.wanban.downloader.model.M3U8Seg
import com.wanban.downloader.model.M3U8Stream
import java.io.BufferedWriter
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException


class M3U8ParseUtils {

    // 重新请求
    private  val ERROR_CODE_REQUEST = -999
    // 存在二级请求
    private val ERROR_CODE_MULTI_STREAMS_REQUEST = -998
    private var listener: OnParseListener? = null
    private var httpConnectUtils: HttpConnectUtils? = null
    private var parseTask: ThreadUtils.Task<M3U8>? = null
    /**
     * 解析M3U, 将其转换成M3U8对象
     */
    fun getM3U8Info(url: String, fileName: String, header: Map<String, String>? = null, callback: OnParseListener) {
        listener = callback
        // 结束正在执行的任务
        cancelParse()
        // 执行新任务
        executeParse(url, fileName, header)
    }


    private fun executeParse(url: String, fileName: String, header: Map<String, String>? = null) {
        parseTask = object : ThreadUtils.Task<M3U8>() {
            override fun doInBackground(): M3U8 {
                var m3u8 = M3U8(url)
                // 创建视频下载目录，包括m3u，cover和ts文件等
                m3u8.videoName = fileName
                // 先创建m3u本地缓存解析文件，
                val m3uFilePath = FileDownloadUtils.geM3UFilePath(fileName)
                if (!TextUtils.isEmpty(m3uFilePath) && FileUtils.isFileExists(m3uFilePath) && FileUtils.getLength(m3uFilePath) > 0) {
                    Log.d("M3U8ParseUtils", "M3U存在缓存，直接使用缓存！")
                    return parseM3U8Local(m3u8, m3uFilePath!!)
                }
                Log.d("M3U8ParseUtils", "M3U没有缓存，使用网络！")
                if (url.trim().isEmpty()) {
                    throw HttpConnectionException(ErrorMessage.ERROR_URL_IS_NULL)
                }
                if (!CommonUtils.matchHttpSchema(url)) {
                    throw HttpConnectionException(ErrorMessage.ERROR_SCHEMA_CANNOT_PARSE)
                }
                httpConnectUtils = HttpConnectUtils()
                // 2.请求url
                val connection = httpConnectUtils?.makeConnection(url, header) ?: throw HttpConnectionException(ErrorMessage.ERROR_HTTPCONNECTION_ERROR)
                val responseCode = connection.responseCode
                if (responseCode < 200 || responseCode > 299) {
                    throw HttpConnectionException(ErrorMessage.ERROR_HTTPCONNECTION_ERROR)
                }
                // 解压InputStream
                val inputStream = httpConnectUtils?.compressInputStream(connection) ?: throw HttpConnectionException(ErrorMessage.ERROR_INPUTSTREAM_IS_NULL)
                m3u8.finalUrl = connection.url.toString()
                if (TextUtils.isEmpty(m3u8.finalUrl)) {
                    throw HttpConnectionException(ErrorMessage.ERROR_FINAL_URL_IS_NULL)
                }
                // 根据类型和url判断是否m3u8
                val contentType = connection.contentType
                if (m3u8.finalUrl.contains(Constants.M3U8, ignoreCase = true) || CommonUtils.isM3U8Mimetype(contentType)) { // 是m3u8
                    // 将内容放入内存
                    val m3uString = ConvertUtils.inputStream2String(inputStream, "UTF-8")
                    if (TextUtils.isEmpty(m3uString)) {
                        throw HttpConnectionException(ErrorMessage.ERROR_INPUTSTREAM_IS_NULL)
                    }
                    // 3.解析
                    m3u8 = parseM3U8(m3u8, m3uString.lines())
                    // 如果存在多个轨道流，重亲请求
                    if (m3u8.streams?.isNotEmpty() == true) {
                        m3u8.errorCode = ERROR_CODE_MULTI_STREAMS_REQUEST
                        return m3u8
                    }
                    // 4.根据结果, 缓存m3u，只有解析到最后一层，才会进行缓存
                    createRemoteM3U8(m3u8, header, m3uFilePath, m3uString)
                    // 5.缓存代理m3u
                    createProxyM3U8File(m3u8)
                } else { // mp4
                    m3u8.isM3U8 = false
                    val m3u8Seg = M3U8Seg()
                    m3u8Seg.url = m3u8.finalUrl
                    m3u8Seg.tsSize = connection.contentLength.toLong()
                    m3u8.tsList.add(m3u8Seg)
                }
                m3u8.success = true
                return m3u8
            }

            override fun onCancel() {
                closeConnection()
            }

            override fun onFail(t: Throwable?) {
                closeConnection()
                t?.printStackTrace()
                val m3u8 = M3U8(url)
                m3u8.setErrorMessage(t?.message)
                listener?.onError(m3u8)
            }

            override fun onSuccess(m3u8: M3U8) {
                // 重新解析
                if (m3u8.errorCode == ERROR_CODE_REQUEST) {
                    executeParse(url, fileName, header)
                    return
                }
                // 由于有二级请求，需要再次重新解析
                if (m3u8.errorCode == ERROR_CODE_MULTI_STREAMS_REQUEST && m3u8.streams?.isNotEmpty() == true) {
                    Log.e("MyM3U8", "有二级请求列表")
                    // 按带宽或分辨率等信息进行排序
                    m3u8.streams.sort()
                    // 取视频清晰度最高的流
                    val stream = m3u8.streams.last()
                    // 重新解析
                    executeParse(stream.url, fileName, header)
                    return
                }
                // 获得最后的解析结果
                if (m3u8.isM3U8 && m3u8.tsList.isEmpty()) {
                    m3u8.setErrorMessage("ts is empty!")
                    listener?.onError(m3u8)
                } else {
                    listener?.onSuccess(m3u8)
                }

            }

        }
        parseTask?.setTimeout(Constants.TIME_OUT) {
            val m3u8 = M3U8(url)
            m3u8.setErrorMessage(ErrorMessage.ERROR_PARSE_URL_TIME_OUT)
            cancelParse()
            listener?.onError(m3u8)
        }
        ThreadUtils.executeByIo(parseTask)
    }


    /**
     * 解析本地M3U
     */
    fun parseM3U8Local(m3u8: M3U8, filePath: String): M3U8 {
        // 读取M3U文件
        try {
            val localM3U = FileIOUtils.readFile2String(filePath)
            if (TextUtils.isEmpty(localM3U)) { // 如果没读取成功，删除文件，重新解析
                FileUtils.delete(filePath)
                m3u8.errorCode = ERROR_CODE_REQUEST
                return m3u8
            }
            // 按行读取内容
            val targetList = ArrayList<String>(localM3U.lines())
            // 最后一行默认是finalUrl
            val finalUrlLocal = targetList.removeLast()
            if (!finalUrlLocal.startsWith("final")) {// 如果找到finalUrl，删除文件，重新解析
                FileUtils.delete(filePath)
                m3u8.errorCode = ERROR_CODE_REQUEST
                return m3u8
            }
            val result = finalUrlLocal.split("=")
            if (result.size < 2) { // 如果找到finalUrl，删除文件，重新解析
                FileUtils.delete(filePath)
                m3u8.errorCode = ERROR_CODE_REQUEST
                return m3u8
            }
            m3u8.finalUrl = result[1]
            m3u8.success = true
            // 找到配置文件，直接解析
            return parseM3U8(m3u8, targetList)
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
            m3u8.errorCode = ERROR_CODE_REQUEST
            return m3u8
        }
    }



    /**
     * 取消解析
     */
    fun cancelParse() {
        closeConnection()
        ThreadUtils.cancel(parseTask)
    }


    /**
     * 缓存m3u文件并追加finalUrl参数
     */
    @Throws(IOException::class)
    fun createRemoteM3U8(m3u8: M3U8, header: Map<String, String>?, m3uFilePath: String?, m3uString: String?) {
        if (TextUtils.isEmpty(m3uFilePath)) return
        if (TextUtils.isEmpty(m3uString)) {
            FileUtils.delete(m3uFilePath)
            return
        }
        val isM3UCreated = FileUtils.createFileByDeleteOldFile(m3uFilePath)
        if (!isM3UCreated) {
            return
        }
        val bfw = BufferedWriter(FileWriter(m3uFilePath, false))
        bfw.write(Constants.TAG_PLAYLIST_HEADER + "\n")
        bfw.write((Constants.TAG_VERSION + ":" + m3u8.version) + "\n")
        bfw.write(Constants.TAG_MEDIA_SEQUENCE + ":" + m3u8.sequence + "\n")
        bfw.write(Constants.TAG_TARGET_DURATION + ":" + m3u8.targetDuration + "\n")
        for (m3u8Ts in m3u8.tsList) {
            if (m3u8Ts.hasInitSegment) {
                val initSegmentInfo = if (m3u8Ts.segmentByteRange != null) {
                    "URI=\"" + m3u8Ts.initSegmentUri + "\"" + ",BYTERANGE=\"" + m3u8Ts.segmentByteRange + "\""
                } else {
                    "URI=\"" + m3u8Ts.initSegmentUri + "\""
                }
                bfw.write(Constants.TAG_INIT_SEGMENT + ":" + initSegmentInfo + "\n")
            }
            if (m3u8Ts.hasKey) {
                if (m3u8Ts.method != null) {
                    var key = "METHOD=" + m3u8Ts.method
                    if (m3u8Ts.keyUri != null) {
                        val keyUri = m3u8Ts.keyUri
                        key += ",URI=\"$keyUri\""
                        val httpConnectUtils = HttpConnectUtils()
                        val connection = httpConnectUtils.makeConnection(keyUri, header)
                        val dis = DataInputStream(connection.inputStream)
                        val keyFileDirPath = FileDownloadUtils.getM3U8KeyFileDirPath(m3u8.videoName)
                        val keyFile = File(keyFileDirPath, m3u8Ts.localKeyUri)
                        FileUtils.createOrExistsFile(keyFile)
                        val dos = DataOutputStream(FileOutputStream(keyFile))
                        val buffer = ByteArray(4096)
                        var count: Int
                        while (dis.read(buffer).also { count = it } > 0) {
                            dos.write(buffer, 0, count)
                        }
                        dis.close()
                        dos.close()
                        if (m3u8Ts.keyIV != null) {
                            key += ",IV=" + m3u8Ts.keyIV
                        }
                    }
                    bfw.write(Constants.TAG_KEY + ":" + key + "\n")
                }
            }
            if (m3u8Ts.hasDiscontinuity) {
                bfw.write(Constants.TAG_DISCONTINUITY + "\n")
            }
            bfw.write(Constants.TAG_MEDIA_DURATION + ":" + m3u8Ts.duration + ",\n")
            bfw.write(m3u8Ts.url)
            bfw.newLine()
        }
        bfw.write(Constants.TAG_ENDLIST)
        bfw.newLine()
        bfw.write("finalUrl=" + m3u8.finalUrl)
        bfw.flush()
        bfw.close()
    }

    /**
     * 创建本地代理的M3U8索引文件
     * @param m3u8
     * @throws Exception
     */
    @Synchronized
    fun createProxyM3U8File(m3u8: M3U8) {
        var bfw: BufferedWriter? = null
        try {
            val proxyM3U8File = FileDownloadUtils.getOrCreateProxyM3U8File(m3u8.videoName)
            bfw = BufferedWriter(FileWriter(proxyM3U8File, false))
            bfw.write(Constants.TAG_PLAYLIST_HEADER + "\n")
            bfw.write("${Constants.TAG_VERSION}:${m3u8.version}" + "\n")
            bfw.write("${Constants.TAG_MEDIA_SEQUENCE}:${m3u8.sequence}" + "\n")
            bfw.write("${Constants.TAG_TARGET_DURATION}:${m3u8.targetDuration}" + "\n")
            val fileDirPath = FileDownloadUtils.getOrCreateTsFileDirPath(m3u8.videoName)
            for (m3u8Ts in m3u8.tsList) {
                val initSegmentFilePath = FileDownloadUtils.getTsFilePath(fileDirPath, m3u8Ts.initSegmentName)
                if (m3u8Ts.hasInitSegment) {
                    val initSegmentInfo = if (m3u8Ts.segmentByteRange != null) {
                        "URI=\"$initSegmentFilePath\",BYTERANGE=\"${m3u8Ts.segmentByteRange}"
                    } else {
                        "URI=\"$initSegmentFilePath\""
                    }
                    bfw.write("${Constants.TAG_INIT_SEGMENT}:$initSegmentInfo" + "\n")
                }
                if (m3u8Ts.hasKey && !TextUtils.isEmpty(m3u8Ts.method)) {
                    var key = "METHOD=${m3u8Ts.method}"
                    if (!TextUtils.isEmpty(m3u8Ts.keyUri)) {
                        val keyFile = FileDownloadUtils.getM3U8KeyFileDirPath(m3u8.videoName) + File.separator + m3u8Ts.localKeyUri
                        key += ",URI=\"$keyFile\""
                        if (m3u8Ts.keyIV != null) {
                            key += ",IV=" + m3u8Ts.keyIV
                        }
                    }
                    bfw.write("${Constants.TAG_KEY}:$key" + "\n")
                }
                if (m3u8Ts.hasDiscontinuity) {
                    bfw.write(Constants.TAG_DISCONTINUITY + "\n")
                }
                bfw.write("${Constants.TAG_MEDIA_DURATION}:${m3u8Ts.duration},\n")
                val tsFilePath = FileDownloadUtils.getTsFilePath(fileDirPath, m3u8Ts.indexName)
                bfw.write(tsFilePath)
                bfw.newLine()
            }
            bfw.write(Constants.TAG_ENDLIST)
            bfw.flush()
            Log.e("M3U8ParseUtils", "代理解析文件下载完成...")
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
            CloseUtils.closeIO(bfw)
        }

    }
    private fun parseM3U8(m3u8: M3U8, m3uLines: List<String>?): M3U8 {
        return try {
            if (m3uLines.isNullOrEmpty()) {
                m3u8.setErrorMessage(ErrorMessage.ERROR_INPUTSTREAM_IS_NULL)
                return m3u8
            }
            m3u8.isM3U8 = true
            var tsDuration = 0f
            var targetDuration = 0f
            var tsIndex = 0
            var version = 0
            var sequence = 0
            var hasDiscontinuity = false
            var hasEndList = false
            var hasKey = false
            var hasInitSegment = false
            var isIFrameOnlyVariant = false
            var method: String? = null
            var encryptionIV: String? = null
            var encryptionKeyUri: String? = null
            var initSegmentUri: String? = null
            var segmentByteRange: String? = null
            val streams = ArrayList<M3U8Stream>()

            for (readLine in  m3uLines) {
                readLine.trim { it <= ' ' }
                if (readLine.isEmpty()) continue
                var line = readLine
                if (line.startsWith(Constants.TAG_PREFIX)) { // 标签命令行
                    if (line.startsWith(Constants.TAG_MEDIA_DURATION)) { // 每个片段的时长
                        val ret = CommonUtils.parseStringAttr(line, Constants.REGEX_MEDIA_DURATION)
                        tsDuration = ret?.toFloatOrNull() ?: 0f
                    } else if (line.startsWith(Constants.TAG_I_FRAME_STREAM_INF)) {
                        isIFrameOnlyVariant = true
                    } else if (line.startsWith(Constants.TAG_VERSION)) {
                        val ret = CommonUtils.parseStringAttr(line, Constants.REGEX_VERSION)
                        version = ret?.toIntOrNull() ?: 0
                    } else if (line.startsWith(Constants.TAG_MEDIA_SEQUENCE)) {
                        val ret = CommonUtils.parseStringAttr(line, Constants.REGEX_MEDIA_SEQUENCE)
                        if (!TextUtils.isEmpty(ret)) {
                            sequence = ret?.toIntOrNull() ?: 0
                        }
                    } else if (line.startsWith(Constants.TAG_TARGET_DURATION)) {
                        val ret = CommonUtils.parseStringAttr(line, Constants.REGEX_TARGET_DURATION)
                        if (!TextUtils.isEmpty(ret)) {
                            targetDuration = ret?.toFloatOrNull()?:0f
                        }
                    }else if (line.startsWith(Constants.TAG_STREAM_INF) || isIFrameOnlyVariant) {
                        val stream = M3U8Stream()
                        // val ret = parseMultiStringAttr(line, REGEX_BANDWIDTH)
                        // 获取可变码流中的峰值比特率，单位bits/s
                        if (line.contains(Constants.BANDWIDTH_PRE)) {
                            line = line.replace(Constants.TAG_STREAM_INF_PRE, "")
                            val result = line.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                            for (tag in result) {
                                val target = tag.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                                if (target.size <= 1) continue
                                val extTag = target[0].trim { it <= ' ' }
                                val value = target[1]
                                if (Constants.BANDWIDTH.equals(extTag, ignoreCase = true)) {
                                    stream.peakBitrate = value.toInt()
                                } else if (Constants.AVERAGE_BANDWIDTH.equals(extTag, ignoreCase = true)) {
                                    stream.averageBitrate = value.toInt()
                                } else if (Constants.CODECS.equals(extTag, ignoreCase = true)) {
                                    stream.codecs = value
                                } else if (Constants.RESOLUTION.equals(extTag, ignoreCase = true)) {
                                    val widthAndHeight = value.split("x").toTypedArray()
                                    stream.width = widthAndHeight[0].toIntOrNull() ?: -1
                                    stream.height = widthAndHeight[1].toIntOrNull() ?: -1
                                    if (stream.width <= 0 || stream.height <= 0) {
                                        // Resolution string is invalid.
                                        stream.width = -1
                                        stream.height = -1
                                    }
                                } else if (Constants.FRAME_RATE.equals(extTag, ignoreCase = true)) {
                                    stream.frameRate = value.toFloat()
                                } else if (Constants.VIDEO.equals(extTag, ignoreCase = true)) {
                                    stream.videoGroupId = value
                                } else if (Constants.AUDIO.equals(extTag, ignoreCase = true)) {
                                    stream.audioGroupId = value
                                } else if (isIFrameOnlyVariant && Constants.URI.equals(extTag, ignoreCase = true)) {
                                    stream.url = value
                                }
                            }
                        }
                        streams.add(stream)
                    } else if (line.startsWith(Constants.TAG_DISCONTINUITY)) {
                        hasDiscontinuity = true
                    } else if (line.startsWith(Constants.TAG_ENDLIST)) {
                        hasEndList = true
                    } else if (line.startsWith(Constants.TAG_KEY)) {
                        hasKey = true
                        method = CommonUtils.parseOptionalStringAttr(line, Constants.REGEX_METHOD)
                        val keyFormat = CommonUtils.parseOptionalStringAttr(line, Constants.REGEX_KEYFORMAT)
                        if (Constants.METHOD_NONE != method) {
                            encryptionIV = CommonUtils.parseOptionalStringAttr(line, Constants.REGEX_IV)
                            if (Constants.KEYFORMAT_IDENTITY == keyFormat || keyFormat == null) {
                                if (Constants.METHOD_AES_128 == method) {
                                    // The segment is fully encrypted using an identity key.
                                    val tempKeyUri = CommonUtils.parseStringAttr(line, Constants.REGEX_URI)
                                    if (tempKeyUri != null) {
                                        encryptionKeyUri = CommonUtils.getM3U8AbsoluteUrl(m3u8.finalUrl, tempKeyUri)
                                    }
                                } else {
                                    // Do nothing. Samples are encrypted using an identity key,
                                    // but this is not supported. Hopefully, a traditional DRM
                                    // alternative is also provided.
                                }
                            } else {
                                // Do nothing.
                            }
                        }

                    } else if (line.startsWith(Constants.TAG_INIT_SEGMENT)) {
                        val tempInitSegmentUri = CommonUtils.parseStringAttr(line, Constants.REGEX_URI)
                        if (tempInitSegmentUri != null) {
                            hasInitSegment = true
                            initSegmentUri = CommonUtils.getM3U8AbsoluteUrl(m3u8.finalUrl, tempInitSegmentUri)
                            segmentByteRange = CommonUtils.parseOptionalStringAttr(line, Constants.REGEX_ATTR_BYTERANGE)
                        }
                    }
                    continue
                }
                if (streams.isNotEmpty()) {
                    val last = streams[streams.size - 1]
                    if (!isIFrameOnlyVariant) {
                        last.url = CommonUtils.getM3U8AbsoluteUrl(m3u8.finalUrl, line)
                    }
                    continue
                }

                val ts = M3U8Seg()
                ts.parentUrl = m3u8.finalUrl
                ts.initTsAttributes(CommonUtils.getM3U8AbsoluteUrl(m3u8.finalUrl, line), tsDuration, tsIndex, sequence++, hasDiscontinuity)
                if (hasKey) {
                    ts.setKeyConfig(method, encryptionKeyUri, encryptionIV)
                }
                if (hasInitSegment) {
                    ts.setInitSegmentInfo(initSegmentUri, segmentByteRange)
                }
                m3u8.addTs(ts)
                tsIndex++
                tsDuration = 0f
                hasDiscontinuity = false
                hasKey = false
                hasInitSegment = false
                method = null
                encryptionKeyUri = null
                encryptionIV = null
                initSegmentUri = null
                segmentByteRange = null
            }
            m3u8.streams = streams
            m3u8.targetDuration = targetDuration
            m3u8.version = version
            m3u8.sequence = sequence
            m3u8.hasEndList = hasEndList
            m3u8
        } catch (e: Exception) {
            e.printStackTrace()
            m3u8.setErrorMessage(e.message)
            m3u8
        }
    }

    private fun closeConnection() {
        httpConnectUtils?.close()
        httpConnectUtils = null
    }


}